package tsm.network;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.SocketConnection;

public class NetworkCore {
	// public static MoonMessageHandler networkHandler = new
	// MoonMessageHandler();
	private static DataOutputStream outputStream;
	private static InputStream inputStream;
	private static SocketConnection sc;
	private static MessageSender sender;
	private static boolean connected, connecting, connectfail;
	//	private static int sendByteCount;
	public static int recvByteCount;
	private static int HEADER_LEN = 4;

	public static boolean isConnected() {
		return connected;
	}

	public static boolean isConnecting() {
		return connecting;
	}

	public static void connect(String host, int port) {
//		System.out.println("[NETWORKUTIL] IP: " + host + " Port: " + port);
		if (connected || connecting) {
			return;
		} else {
			connecting = true;
			sender = new MessageSender(host, port);
			Thread sendThread = new Thread(sender);
			sendThread.start();
		}
	}

	public static void sendMessage(Message message) {
		sender.addMessage(message);
	}

	private static void close() {
		//		System.out.println("[NETWORKUTIL] close begin");
		if (connected) {
			BeUMessageHandler.instance.onDisconnected();
		} else if (connecting) {
			BeUMessageHandler.instance.onConnectionFail();
		}
		connectfail = true;
		connected = false;
		connecting = false;
		if (sc != null) {
			try {
				sc.close();
			} catch (IOException e) {
			}
			sc = null;
		}
		if (outputStream != null) {
			try {
				outputStream.close();
			} catch (IOException e) {
			}
			outputStream = null;
		}
		if (inputStream != null) {
			try {
				inputStream.close();
			} catch (IOException e) {
			}
			inputStream = null;
		}
		System.gc();
		//		System.out.println("[NETWORKUTIL] close done");
	}

	public static void forceClose() { //using from app layer
		//		System.out.println("[NETWORKUTIL] forceClose called close network");
		if (!connectfail) {
			NetworkCore.close();
		}
	}

	static class MessageSender implements Runnable {
		private final String host;
		private final int port;
		private final Vector sendingMessage;

		public MessageSender(String host, int port) {
			sendingMessage = new Vector();
			this.host = host;
			this.port = port;
		}

		public void addMessage(Message message) {
			sendingMessage.addElement(message);
		}

		public void run() {
			Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
			try {
				doConnect();
			} catch (Exception ex) {
				close();
				return;
			}
			Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
			while (connected) {
				try {
					while (sendingMessage.size() > 0) {
						//						System.out.println("[NETWORKUTIL] sendingMsg....");
						Message msg = (Message) sendingMessage.elementAt(0);
						sendingMessage.removeElementAt(0);
						byte[] msgData = msg.toByteArray();
						int msgLength = msgData.length;
						// write packet length
						for (int i = 3; i >= 0; i--) {
							outputStream.write(msgLength >> (i * 8));
						}
						// write out data
						outputStream.write(msgData, 0, msgData.length);
						//						sendByteCount += msgLength + 4;
						outputStream.flush();
						msg.dispose();
						msg = null;
						//						System.out.println("[NETWORKUTIL] sendingMsg done....");
					}
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} catch (Exception ex) {
					//					System.out.println("[NETWORKUTIL] =========== MessageSender " + ex.toString());
					ex.printStackTrace();
					//					System.out.println("[NETWORKUTIL] MessageSender called close network");
					if (!connectfail) {
						NetworkCore.close();
					}
					break;
				}
			}
		}

		public void doConnect() throws Exception {
			try {
				sc = (SocketConnection) Connector.open("socket://" + host + ":" + port);
			} catch (Exception e) {
				throw e;
			}
			try {
				sc.setSocketOption(SocketConnection.KEEPALIVE, 1);
			} catch (Exception e) {
			}
			try {
				sc.setSocketOption(SocketConnection.DELAY, 1);
			} catch (Exception e) {
			}

			outputStream = sc.openDataOutputStream();
			inputStream = sc.openInputStream();
			connected = true;
			connectfail = false;
			connecting = false;
			Thread collectorThread = new Thread(new MessageCollector());
			collectorThread.start();
			BeUMessageHandler.instance.onConnectOK();
		}
	}

	static class MessageCollector implements Runnable {

		public void run() {
			byte[] message;
			while (connected) {
				try {
					message = readMessage();
					if (message != null) {
						try {
							BeUMessageHandler.instance.processMessage(message);
						} catch (Exception e) {
							//							System.out.println("[NETWORKUTIL] =========== MessageCollector " + e.toString());
							//							e.printStackTrace();
						}
					} else {
						break;
					}
				} catch (IOException e1) {
					//					System.out.println("[NETWORKUTIL] =========== MessageCollector " + e1.toString());
					//					e1.printStackTrace();
					//					System.out.println("[NETWORKUTIL] MessageCollector called close network");
					if (!connectfail) {
						NetworkCore.close();
					}
					break;
				}
			}

		}

		private byte[] readMessage() throws IOException {
			//			System.out.println("[NETWORKUTIL] Begin read msg");
			// read packet length

			byte[] header = new byte[HEADER_LEN];
			int offset = 0;
			int len = inputStream.read(header, 0, HEADER_LEN);
			while (len > -1 && offset < HEADER_LEN) {
				offset += len;
				len = inputStream.read(header, offset, HEADER_LEN - offset);
			}
			if (len == -1) {
				return null;
			}
			// read data buffer
			int packetLen = bytes2Int(header);
			byte[] data = new byte[packetLen];
			offset = 0;
			//			System.out.println("[NETWORKUTIL]  Read msg 5");
			len = inputStream.read(data, 0, packetLen);
			//			System.out.println("[NETWORKUTIL]  Read msg 6");
			while (len > -1 && offset < packetLen) {
				offset += len;
				len = inputStream.read(data, offset, packetLen - offset);
			}
			if (len == -1) {
				return null;
			}
			recvByteCount += (HEADER_LEN + packetLen);
			//			System.out.println("[NETWORKUTIL]  Current packetLen = " + data.length + " byte(s)");
			//			System.out.println("[NETWORKUTIL]  Total recvByteCount = " + recvByteCount + " byte(s)");
			return data;
		}

	}

	public static int bytes2Int(byte[] data) {
		int res = 0;
		for (int i = 0; i < 4; i++) {
			res <<= 8;
			res |= (0xFF & data[i]);
		}
		return res;
	}

}
